package pl;

import java.util.ArrayList;

public class analizadorSintactico {

    public analizadorLexico lexi;
    public String eixida;
    public nodoToken davanter;
    public boolean mainTrobat;
    public TablaSimbolos tauleta;
    public clase tb;

    public analizadorSintactico(analizadorLexico anna) throws nodoToken {

        mainTrobat = false;
        eixida = new String();
        davanter = new nodoToken();
        tauleta = new TablaSimbolos(null);
        lexi = anna;
    }
     public void aparellar(String token_estandar) throws nodoToken, Error {

        if (token_estandar.equals(davanter.token)) {
            davanter = lexi.esprem();

        } else {
            throw new Error(token_estandar, 2, davanter);
        }
    }
// Aquí comienza y termina el análisis
    public String pinta() throws nodoToken, Error {
        davanter = lexi.esprem();

        eixida = S();

        if (!davanter.token.equals("EOF")) {/*throw Error();*/

        } else if (!mainTrobat) {
            throw new Error("", 9, davanter);
        }
        return eixida;
    }

    public String S() throws nodoToken, Error {

        String s = "";
        if (davanter.token.equals((String) "class")) {
            s += C();
            s += S();
        } else if (davanter.token.equals((String) "EOF")) {
        } else {
            throw new Error("'class' final de fichero", 3, davanter);
        }
        return s;
    }

    public String C() throws nodoToken, Error {
        String c = "";

        aparellar("class");
        c = davanter.cadena;    // ahora c contiene el nombre de la clase que comenzamos a analizar
        if (tauleta.existeSimbolo(c)) {
            // Si la clase existe en este ámbito, se genera un error
            throw new Error(c, 5, davanter);
        } else {
            // Si no existe, se introduce como símbolo en la TS actual
            tauleta.nuevoSimbolo(c, 3/*CLASS*/, 0,0);
            aparellar("id");
            aparellar("llavei");
            // Se genera una nueva TS asociada a la clase nueva que apunta a la TS actual como padre
            // como solo trabajamos sobre una TS en cada momento, no es necesario darle otro nombre
            tauleta = new TablaSimbolos(tauleta);
            // creamos una instancia de la clase 'clase' para almacenar las variables y los módulos de ésta
            tb = new clase();
            tb.setCabeza("class " + c + " {\n");
            D(tb);
            aparellar("llaved");
            tb.usa(tauleta);
            tauleta = tauleta.pop();
            tb.setFi(ponBlancos(tauleta.getNum()) + "}\n");
        }

        return tb.toString();
    }

    public void D(clase tb) throws nodoToken, Error {
//        String d = "";
        if (davanter.token.equals((String) "double")
                || davanter.token.equals((String) "int")
                || davanter.token.equals((String) "void")) {
//            d += E();
//            d += D();
            E(tb);
            D(tb);
        } else if (davanter.token.equals((String) "llaved")) {
        } else {
            throw new Error("'double' 'int' 'void' '}'", 3, davanter);
        }
//        return d;
    }

    public void E(clase tb) throws nodoToken, Error {
        ArrayList<String> e = null;

        if (davanter.token.equals((String) "double") || davanter.token.equals((String) "int")) {
            e = V();
            tb.incorporaVariable(e.get(1), e.get(0), tauleta.getNum());
        } else if (davanter.token.equals((String) "void")) {
//            e += M();
            M(tb);
        } else {
            throw new Error("'double' 'int' 'void'", 3, davanter);
        }
//        return e;
    }

    public ArrayList<String> V() throws nodoToken, Error {
        ArrayList<String> par = new ArrayList<String>();
        String v = ponBlancos(tauleta.getNum());
        int tipo = 0;
        if (davanter.token.equals("double")) {
            aparellar("double");
            tipo = 2;
            v += "double ";
        } else if (davanter.token.equals("int")) {
            aparellar("int");
            tipo = 1;
            v += "int ";
        }
        if (tipo > 0) {
            if (tauleta.existeSimbolo(davanter.cadena)) {
                throw new Error(davanter.cadena, 5, davanter);
            } else {
                par.add(davanter.cadena);
                v += davanter.cadena + ";";
                tauleta.nuevoSimbolo(davanter.cadena, 1/*VAR*/, tipo,0);
                aparellar("id");
                aparellar("pyc");
            }
        } else {
            throw new Error("'double' 'int'", 3, davanter);
        }
        par.add(v + "\n");
        return par;
    }

    public void M(clase tb) throws nodoToken, Error {

        String m = ponBlancos(tauleta.getNum());
        nodoModulo entrepa;

        aparellar("void");
        m = Mid();
        aparellar("pari");
        aparellar("pard");
        aparellar("llavei");
        entrepa = new nodoModulo(tauleta.getNum(), m, ponBlancos(tauleta.getNum()) + "void " + m + " () {\n");
        tauleta = new TablaSimbolos(tauleta);
//        entrepa.variables= new listaVariables();
        Decl(entrepa.variables);
        entrepa.asignaciones = new ArrayList<asignacion>();
        Cuerpo(entrepa.asignaciones);
        aparellar("llaved");
        entrepa.usa(tauleta);   // indicamos cuales han sido usados
        tauleta = tauleta.pop();
        entrepa.setFi(ponBlancos(tauleta.getNum()) + "}\n");

        tb.modulos.modulos.add(entrepa);
//        return m;
    }

    public String Mid() throws nodoToken, Error {

        String mid = davanter.cadena;
        if (tauleta.existeSimbolo(mid)) {
            throw new Error(mid, 5, davanter);
        } else {
            if (davanter.token.equals((String) "id")) {
                tauleta.nuevoSimbolo(davanter.cadena, 2/*MÉTODO*/, 3 /*no es main*/,0);
                aparellar("id");
            } else if (davanter.token.equals((String) "main")) {
                tauleta.nuevoSimbolo(davanter.cadena, 2/*MÉTODO*/, 4 /*es main*/,0);
                aparellar("main");
                mainTrobat = true;
            } else {
                throw new Error("'main' identificador", 3, davanter);
            }
        }
        return mid;
    }

    public void Decl(listaVariables listaV) throws nodoToken, Error {
        ArrayList<String> par = null;
        if (davanter.token.equals((String) "double") || davanter.token.equals((String) "int")) {
            par = V();
            listaV.mete(par.get(0), par.get(1), tauleta.getNum());
            Decl(listaV);

        } else if (davanter.token.equals((String) "id") || davanter.token.equals((String) "llaved")) {
        } else {
            throw new Error("'double' 'int' '}' identificador", 3, davanter);
        }
//        return decl;
    }

    public void Cuerpo(ArrayList<asignacion> asigS) throws nodoToken, Error {

//        String cuerpo = "";

        if (davanter.token.equals((String) "id")) {
            asigS.add(new asignacion(Inst()));
            Cuerpo(asigS);
        } else if (davanter.token.equals((String) "llaved")) {
        } else {
            throw new Error("'}' identificador", 3, davanter);
        }
//        return cuerpo;
    }

    public String Inst() throws nodoToken, Error {
        String inst = ponBlancos(tauleta.getNum());
        String id = davanter.cadena;
        nodoToken nt = davanter;


        aparellar("id");
        inst += id + Asig(nt);

        tauleta.busca(id).setEmprat(); // se ha usado la variable o método

        aparellar("pyc");

        return inst + ";\n";
    }

    public String Asig(nodoToken id) throws nodoToken, Error {

        String asig = "";
        Simbolo sim = tauleta.busca(id.cadena);

        if (sim != null) {

            if (davanter.token.equals((String) "pari")) {
                if (sim.esMETODO()) {
                    aparellar("pari");
                    aparellar("pard");
                    asig = " ()";
                } else {
                    throw new Error(id.cadena, 8, id);
                }
            } else if (davanter.token.equals((String) "asig")) {
                if (sim.esVAR()) {
                    aparellar("asig");
                    asig = " = " + Factor();
                } else {
                    throw new Error(id.cadena, 7, id);
                }
            } else {
                throw new Error("'(' '='", 3, davanter);
            }
        } else {
            throw new Error(id.cadena, 6, id);
        }
        return asig;
    }

    public String Factor() throws nodoToken, Error {

        /* No puedo devolver directamente davanter.cadena al final
         * porque aparellar pide un nuevo token
         */
        String factor = davanter.cadena;
        nodoToken juja = davanter;
        Simbolo sim;
        if (davanter.token.equals((String) "real")) {
            aparellar("real");
        } else if (davanter.token.equals((String) "entero")) {
            aparellar("entero");
        } else if (davanter.token.equals((String) "id")) {
            aparellar("id"); // si hay error léxico, de aquí no pasa
            sim = tauleta.busca(factor);
            if (sim == null) {
                throw new Error(factor, 6, juja); // no declarada
            } else if (!sim.esVAR()) {
                throw new Error(factor, 7, juja); // no es una variable
            } else {
                sim.setEmprat();
            }
        } else {
            throw new Error("entero identificador real", 3, juja);
        }
        return factor;
    }

    public String ponBlancos(int num) {
        String cad = "";
        for (int i = 0; i < num * 4; i++) {
            cad += " ";
        }
        return cad;
    }
}